using System;
using System.Collections.Generic;
using TTXNA.src.Engine.Graphics;
using TTXNA.src.Util;
using TTXNA.src.Engine.Industries;
using TTXNA.src.Engine.Stations;
using TTXNA.src.Engine.Exceptions;
using TTXNA.src.Engine.World;
using Microsoft.Xna.Framework;

namespace TTXNA.src.Engine.Terrain
{
    public class Map : GameObject, TTUpdateable
    {
        private List<CornerHeight> beforeUpdate = new List<CornerHeight>();
        private List<CornerHeight> toUpdate = new List<CornerHeight>();

        private int[,] cornerHeight;
        private Tile[,] terrainTiles;

        private Dictionary<int, int> table = new Dictionary<int, int>();

        private int width;
        private int length;

        public Map(int xLength, int zLength) : base(Vector3.Zero)
        {
            cornerHeight = new int[xLength + 1, zLength + 1];

            for (int z = 0; z < zLength + 1; z++)
            {
                for (int x = 0; x < xLength + 1; x++)
                {
                    EditLand(new CornerHeight(x, z, 0));
                }
            }

            terrainTiles = new Tile[xLength, zLength];

            this.width = xLength;
            this.length = zLength;

            objectInfo = new ObjectInfo(null, ObjectType.Terrain, "Terrain");
        }

        public bool CornerCoordExists(int x, int z)
        {
            if (x > width || z > length || x < 0 || z < 0)
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        public int GetCornerHeight(int x, int z, Direction dir)
        {
            if (CornerCoordExists(x, z))
            {
                switch (dir)
                {
                    case Direction.NE: if (CornerCoordExists(x + 1, z + 1)) return cornerHeight[x + 1, z + 1]; else break;
                    case Direction.SE: if (CornerCoordExists(x + 1, z)) return cornerHeight[x + 1, z]; else break;
                    case Direction.SW: if (CornerCoordExists(x, z)) return cornerHeight[x, z]; else break;
                    case Direction.NW: if (CornerCoordExists(x, z + 1)) return cornerHeight[x, z + 1]; else break;
                }
            }
            else
                throw new CoordinateOutOfBoundsException();

            return -1;
        }

        public int GetCornerHeight(int x, int z)
        {
            if (CornerCoordExists(x, z))
            {
                return cornerHeight[x, z];
            }
            else
                throw new CoordinateOutOfBoundsException();
        }

        public int GetTileHeight(int x, int z)
        {
            table.Clear();

            int ne = GetCornerHeight(x, z, Direction.NE);
            int se = GetCornerHeight(x, z, Direction.SE);
            int sw = GetCornerHeight(x, z, Direction.SW);
            int nw = GetCornerHeight(x, z, Direction.NW);

            int[] array = { ne, se, sw, nw };

            for (int i = 0; i < 4; i++)
            {
                if (table.ContainsKey(array[i]))
                {
                    table[array[i]]++;
                }
                else { table[array[i]] = 1; }
            }

            if (table.Keys.Count == 1) return ne;
            if (table.Keys.Count == 3) return (ne + se + sw + nw) / 4;

            foreach(int i in table.Keys) {
                if (table[i] == 3)
                {
                    return i;
                }
            }

            if (ne == sw)
            {
                return Math.Min(Math.Min(ne, se), sw);
            }
                
            return (ne + se + sw + nw) / 4;
        }

        public int GetTileHeight(Coordinate coord)
        {
            return GetTileHeight(coord.X, coord.Z);
        }

        public SlopeType GetTileSlopeType(int x, int z)
        {
            table.Clear();

            int ne = GetCornerHeight(x, z, Direction.NE);
            int se = GetCornerHeight(x, z, Direction.SE);
            int sw = GetCornerHeight(x, z, Direction.SW);
            int nw = GetCornerHeight(x, z, Direction.NW);

            int[] array = { ne, se, sw, nw };

            for (int i = 0; i < 4; i++)
            {
                if (table.ContainsKey(array[i]))
                {
                    table[array[i]]++;
                }
                else { table[array[i]] = 1; }
            }

            if (table.Keys.Count == 1) return SlopeType.Flat;
            if (table.Keys.Count == 3) return SlopeType.SlantSlope;

            foreach (int i in table.Keys)
            {
                if (table[i] == 3)
                {
                    return SlopeType.CornerSlope;
                }
            }

            if (ne == sw)
            {
                return SlopeType.DoubleCornerSlope;
            }

            return SlopeType.FlatSlope;
        }

        public int GetTileMaxHeight(int x, int z)
        {
            if (IsValid(x, z))
            {

                int ne = GetCornerHeight(x, z, Direction.NE);
                int se = GetCornerHeight(x, z, Direction.SE);
                int sw = GetCornerHeight(x, z, Direction.SW);
                int nw = GetCornerHeight(x, z, Direction.NW);

                return Math.Max(Math.Max(ne, se), Math.Max(sw, nw));

            }
            else return 0;
        }

        public int GetTileMinHeight(int x, int z)
        {
            if (IsValid(x, z))
            {

                int ne = GetCornerHeight(x, z, Direction.NE);
                int se = GetCornerHeight(x, z, Direction.SE);
                int sw = GetCornerHeight(x, z, Direction.SW);
                int nw = GetCornerHeight(x, z, Direction.NW);

                return Math.Min(Math.Min(ne, se), Math.Min(sw, nw));

            }
            else return 0;
        }

        public Tile GetTile(DDDCoordinate coord)
        {
            if (terrainTiles[coord.X, coord.Z] == null)
            {
                CreateTile(coord.X, coord.Z);
            }

            return GetTile(coord.X, coord.Z);
        }

        public Tile GetTile(Coordinate coord)
        {
            if (terrainTiles[coord.X, coord.Z] == null)
            {
                CreateTile(coord.X, coord.Z);
            }

            return GetTile(coord.X, coord.Z);
        }

        public List<Coordinate> GetSharedTiles(CornerHeight ch)
        {
            List<Coordinate> list = new List<Coordinate>();

            int x = ch.X;
            int z = ch.Z;

            if(IsValid(x, z))
                list.Add(new Coordinate(x, z));
            if (IsValid(x, z - 1))
                list.Add(new Coordinate(x, z - 1));
            if (IsValid(x - 1, z - 1))
                list.Add(new Coordinate(x - 1, z - 1));
            if (IsValid(x - 1, z))
                list.Add(new Coordinate(x - 1, z));
                
            return list;
        }

        public bool IsBuildingOnTile(int x, int z)
        {
            return terrainTiles[x, z].BuildingOnTile;
        }

        private void CreateTile(Coordinate coord)
        {
            terrainTiles[coord.X, coord.Z] = new Tile(new DDDCoordinate(coord.X, GetTileHeight(coord.X, coord.Z), coord.Z)); 
        }

        private void CreateTile(int x, int z)
        {
            terrainTiles[x, z] = new Tile(new DDDCoordinate(x, GetTileHeight(x,z), z));
        }

        public bool AddGameObject(Coordinate coord, GameObject obj) 
        {
            if (terrainTiles[coord.X, coord.Z] == null)
            {
                CreateTile(coord);
            }

            return terrainTiles[coord.X, coord.Z].AddGameObject(obj);
        }

        public Industry GetIndustryOnTile(int x, int z)
        {
            if (!IsValid(x, z) || terrainTiles[x, z] == null) return null;
            else
            {
                return terrainTiles[x, z].IndustryOnTile;
            }
        }

        public LandType GetLandType(int x, int z)
        {
            if (terrainTiles[x, z] == null)
            {
                CreateTile(new Coordinate(x, z));
            }

            return terrainTiles[x, z].LandType;
        }

        public Station GetStationOnTile(int x, int z)
        {
            if (!IsValid(x, z) || terrainTiles[x, z] == null) return null;
            else
            {
                return terrainTiles[x, z].StationOnTile;
            }
        }

        public bool IsValid(int x, int z)
        {
            return (x < width && z < length && x > -1 && z > -1);
        }

        public bool IsValid(Coordinate coord)
        {
            return IsValid(coord.X, coord.Z);
        }

        public Tile GetTile(int x, int z)
        {
            if (!IsValid(x, z))
            {
                throw new CoordinateOutOfBoundsException();
            }
            else
            {
                return terrainTiles[x, z];
            }
        }

        public bool SetTile(Tile tile)
        {
            terrainTiles[tile.Coordinate.X, tile.Coordinate.Z] = tile;
            return true;
        }

        public void EditLand(CornerHeight vert)
        {
            beforeUpdate.Add(new CornerHeight(vert.X, vert.Z, cornerHeight[vert.X, vert.Z]));

            cornerHeight[vert.X, vert.Z] = vert.Height;
            toUpdate.Add(vert);
        }

        public void UndoChanges()
        {
            foreach (CornerHeight ch in beforeUpdate)
            {
                cornerHeight[ch.X, ch.Z] = ch.Height;
            }
            beforeUpdate.Clear();
            toUpdate.Clear();
        }

        public int Width
        {
            get { return width; }
        }

        public int Length
        {
            get { return length; }
        }

        public List<CornerHeight> ToUpdate
        {
            get { return toUpdate; }
        }

        public void ClearChanges()
        {
            toUpdate.Clear();
            beforeUpdate.Clear();
        }

        #region TTUpdateable Members

        public void Update(GameTime elapsedTime)
        {
            ClearChanges();
        }

        #endregion
    }

    public struct CornerHeight
    {
        private int _x;
        private int _z;
        private int _height;

        public CornerHeight(int x, int z, int height)
        {
            _x = x;
            _z = z;
            _height = height;
        }

        public int X { get { return _x; } }
        public int Z { get { return _z; } }

        public void SetHeight(int height)
        {
            _height = height;
        }

        public int Height 
        { 
            get { return _height; }
            set { _height = value; }
        }


        internal static List<CornerHeight> GetVertexNeighbours(CornerHeight source)
        {
            List<CornerHeight> neighbours = new List<CornerHeight>();

            int x = source._x;
            int z = source._z;

            if (z < TTXNAGame.World.Length - 1)
                neighbours.Add(new CornerHeight(x, z + 1, TTXNAGame.World.Map.GetCornerHeight(x, z + 1)));

            if (x < TTXNAGame.World.Width - 1)
                neighbours.Add(new CornerHeight(x + 1, z, TTXNAGame.World.Map.GetCornerHeight(x + 1, z)));

            if (z > 0)
                neighbours.Add(new CornerHeight(x, z - 1, TTXNAGame.World.Map.GetCornerHeight(x, z - 1)));

            if (x > 0)
                neighbours.Add(new CornerHeight(x - 1, z, TTXNAGame.World.Map.GetCornerHeight(x - 1, z)));

            return neighbours;
        }

    }
}
